home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-02-12 | 19.2 KB | 438 lines | [TEXT/CCL2] |
- ;;; ================================================================
- ;;;
- ;;; The Dungeon Master
- ;;;
- ;;; An alternative module system
- ;;;
- ;;; Version 1.0
- ;;;
- ;;; Recommendations for use
- ;;;
- ;;; ================================================================
-
- ;;; Copyright (c) 1993 Massachusetts Institute of Technology.
- ;;; Permission is granted to use this documentation and to pass on copies,
- ;;; provided that this copyright notice is retained intact. In addition,
- ;;; permission is granted to modify this documentation, provided all
- ;;; changes are prominently marked. For more details, refer to the
- ;;; accompanying file "dm-copyright.text".
-
- ;;; Brought to you by the Laboratory for Advanced Technology in the
- ;;; Humantities (formerly the Athena Language Learning Project) at MIT.
- ;;; "Bigger! Better! Slower!"
-
- ;;; Software by Sue Felshin and Stuart Malone. Documentation by Sue
- ;;; Felshin.
-
- ;;; The Dungeon Master system consists of six files:
- ;;; 1) source code ("dungeon-master.lisp"),
- ;;; 2) a detailed copyright notice ("copyright.text"),
- ;;; 3) user documentation in mss (Scribe) format ("dm.mss"),
- ;;; 4) PostScript format ("dm.text"), and
- ;;; 5) text format, and
- ;;; 6) a description of how to use the Dungeon Master ("dm-use.lisp").
- ;;; This is file number 6.
-
- ;;; If you improve the Dungeon Master, you are encouraged to mail changes
- ;;; to sfelshin@athena.mit.edu.
-
- ;;; This file recommends how to use the Dungeon Master by giving an example
- ;;; of integrating it into a lisp project.
-
-
- ;;; ================================================================
- ;;;
- ;;; Recommended Use
- ;;;
- ;;; ================================================================
-
- ;;; You will need three files, in addition to the various modules of your
- ;;; system. This document describes these three files, with appropriate
- ;;; example code, then gives an example of a typical module, and finally
- ;;; contains a few general comments on using the Dungeon Master.
-
-
- ;;; ================================================================
- ;;; First file: the Dungeon Master.
-
- ;;; If you use a configuration other than Lucid on the RS/6000 or MCL, you
- ;;; must configure the Dungeon Master for your configuration. Search for
- ;;; calls to CONFIGURE-ME-ERROR in the Dungeon Master file. Add the
- ;;; appropriate code for your lisp. In some cases this will consist merely
- ;;; of adding your lisp's distinguishing feature(s) to an existing #+ form
- ;;; and subtracting them from the associated #-'ed configure-me-error form.
- ;;; In other cases, you will have to add a small amount of code. After you
- ;;; have configured your lisp, please send your changes to
- ;;; sfelshin@athena.mit.edu so that they can be integrated into the
- ;;; official cody of the Dungeon Master.
-
-
- ;;; ================================================================
- ;;; Second file: set up packages.
-
- ;;; Because of the way the package system is defined (or not defined) in
- ;;; CLtL2, it is impossible to reliably define packages in a distributed
- ;;; manner, such that they can also be modified on the fly. Set up a
- ;;; single file for your project which defines _all_ packages, all at once.
-
- ;;; In point of fact, you need only define mutually dependent packages
- ;;; simultaneously. Therefore, you could have multiple package sets, one
- ;;; for each set of mutually dependent packages. However, every time you
- ;;; modify a package in a package set, you must reload not only the
- ;;; modified package set, but also all package sets which depend on that
- ;;; set. You will probably find it most convenient to define as many
- ;;; packages as possible in the same set, even if they are not all mutually
- ;;; dependent.
- ;;;
- ;;; The MIT LATH NLP project, for example, is a general NLP system which
- ;;; can be used with a variety of control modules. Our project takes the
- ;;; tack of defining one package set file for the entire NLP system, plus
- ;;; one package set file for each control module. Each control module
- ;;; package set is dependent on the NLP package set, but the NLP package
- ;;; set is independent of the control modules.
-
- ;;; This example of DEFINE-PACKAGES is an abbreviated version of the code
- ;;; used by the MIT LATH NLP system.
- ;;;
- ;;; The :NAME, :NICKNAMES, :SHADOW, :SHADOWING-IMPORT, :EXPORT, :USE, and
- ;;; :IMPORT keywords related to the package system as described by CLtL2.
- ;;;
- ;;; The :GLOBAL keyword allows symbols to be made "global" (accessible
- ;;; without package prefix from every package defined by DEFINE-PACKAGES)
- ;;; by importing them into exporting them from the DM package, which is
- ;;; used by all packages defined by DEFINE-PACKAGES. It is tasteless to
- ;;; make symbols which are used non-interactively global.
- ;;;
- ;;; Not all Common Lisps are 100% CLtL2 compatible yet. Lisps which do not
- ;;; contain a built-in pretty printer and/or condition system must use the
- ;;; public domain implementations of these systems. If the
- ;;; :PRETTY-PRINTING or :CONDITIONS keyword is given a true value for a
- ;;; package, DEFINE-PACKAGES will ensure that the specified system(s)
- ;;; is/are present and that their code is available to the package.
- ;;;
- ;;; All keywords take lists of symbol names as their values with the
- ;;; exception of the :SHADOWING-IMPORT and :IMPORT keywords. In place of
- ;;; each symbol name, these take a list of a symbol name and the name of
- ;;; the package from which to import the symbol.
-
- (define-packages
-
- ;; ================================================================
- ;; Independent packages (packages which depend on no other packages, but
- ;; which are used by later packages in this form).
-
- ((:name "PROBES")
- (:export
- "PROBING-P" "UNLESS-PROBING" "UNPROBE-QUIETLY" "DEFINE-PROBE"
- "PROBE-QUIETLY" "ENSURE-THAT" "WHEN-PROBING")
- ;; Debugging stuff, to be called interactively. These symbols don't
- ;; have to be explicitly exported, because making them global takes care
- ;; of that.
- (:global "PROBE" "UNPROBE" "PROBES")
- ;; This is an independent package, so it doesn't use anything (except
- ;; the *base-packages*, which DEFINE-PACKAGES will automatically make
- ;; all packages use).
- (:use))
-
- ((:name "BACKTRACE")
- (:export "BACKTRACE-STRING")
- #+lucid
- #.`(:import ,@(mapcar #'(lambda (name) (list name "LUCID"))
- '("*DEBUGGER-FRAMES*" "COLLECT-STACK-FRAMES"
- "NEWEST-UNHIDDEN-STACK-FRAME-INDEX"
- "PREVIOUS-UNHIDDEN-STACK-FRAME-INDEX"
- "STACK-FRAME-NAME" "STACK-FRAME-SOURCE-CODE"
- "STACK-FRAME-LENGTH" "STACK-FRAME-LOCAL-NAME"
- "STACK-FRAME-LOCAL-VALUE" "AVOID-STACK-FRAME-P")))
- (:use))
-
- ;; ================================================================
- ;; Non-mutually dependent packages (packages which depend only on
- ;; independent packages defined higher in this form).
-
- ;; Typical simple package. Export some symbols and then use some
- ;; packages defined in the previous section of this form.
- ((:name "ITER")
- (:export
- "DEFITER" "ITERATE" "YIELD")
- (:use "PROBES"))
-
- ((:name "RESOURCES")
- ;; Lucid 4.0.0 hasn't implemented the COMMON-LISP package yet, and
- ;; exports all kinds of non-Common-Lisp standard symbols from its
- ;; LUCID-COMMON-LISP package.
- #+lucid (:shadow "MAKE-RESOURCE")
- (:export "DEFRESOURCE" "ALLOCATE" "RESET-RESOURCE" "DEALLOCATE")
- (:use "PROBES"))
-
- ((:name "REGISTRAR")
- (:export
- "MAKE-REGISTRY" "REGISTER" "REGISTRY" "DO-REGISTRY"
- "WRITE-REGISTRY-INITIALIZATION" "WRITE-REGISTRY-FINALIZATION"
- "WRITE-REGISTRIES" "WRITE-REFERENCE"
- "*REGISTRY-VECTOR*" "REGISTRAR-READ-MACRO")
- (:use "PROBES")
- ;; The registrar makes use of the pretty printer.
- (:pretty-printing t))
-
- ((:name "TRANSLITERATE")
- (:export
- "TSTRING-DOWNCASE" "TOKEN-NAME" "KEYBOARD-INPUT" "INTERN-TOKEN"
- "UNKNOWN-TOKEN"
- "TOKEN-EQUAL" "TOKEN-LOWER-CASE-P" "TOKEN-STRING" "TSTRING-CAPITALIZE"
- "*STANDARD-SYMBOL-CHARACTER-SET*" "TOKEN-ALPHABETIC-P" "DEFINE-TOKEN"
- "READ-TOKEN-NO-HANG" "LEXICON-EXTERNAL" "TOKEN" "PARSE-TOKEN" "WRITE-TOKEN"
- "LEXICON-INTERNAL" "*STANDARD-CHARACTER-SET*" "READ-TOKEN"
- "TOKEN-UPPER-CASE-P" "TSTRING-EQUAL" "TSTRING-UPCASE" "TSTRING-CAPITULATE"
- "TOKEN-WHITESPACE-P" "TOKEN-PUNCTUATION-P" "TRANSLITERATE-STRING"
- "DEFINE-CHARACTER-SET" "BRACKET-MACRO")
- (:use "ITER" "PROBES")
- ;; The transliterater makes use of the condition system.
- (:conditions t))
-
- ;; ================================================================
- ;; Mutually dependent packages (packages which use each other).
-
- ((:name "HISTORIAN")
- (:nicknames "HIST")
- (:export
- "BEGIN-CHAPTER" "SAVE-HISTORY" "ADD-TO-CHAPTER" "RESET-HISTORIAN"
- "PRINT-HISTORY" "END-CHAPTER" "EMPTY-CHAPTER")
- (:use "INPUT" "TRANSLITERATE"))
-
- ((:name "INPUT")
- ;; See note at package RESOURCES. Under Lucid, *prompt* is used in
- ;; controlling the lisp prompt.
- #+lucid (:shadow "*PROMPT*")
- (:shadowing-import
- ;; Our extension to CLOS. I forget which lisp defines its own
- ;; definstance, so that we must shadow it.
- ("DEFINSTANCE" "ALLP"))
- (:export
- ;; From module :input-conditions.
- "RESET-UNKNOWN-WORD-LIST" "*UNKNOWN-WORD-LIST*" "USE-NIL"
- "*X-INPUT-STREAM*"
- "FATAL-UNKNOWN-WORD-TOKENS" "UNKNOWN-WORD" "UNKNOWN-WORD-TOKENS"
- "UNKNOWN-WORD-SURFACE-STRING" "USE-GENERIC-WORDS"
- "FATAL-UNKNOWN-WORD" "FATAL-UNKNOWN-WORD-SURFACE-STRING"
-
- ;; From module :input.
- "*IO-WINDOW-P*" "REPARSE"
- "PROMPT-INPUT-EDITOR-FOR-LINE" "*X-INPUT-EDITOR*" "*IO-WINDOW-STREAM*"
- "CLOSE-INPUT-EDITOR" "END-OF-INPUT-STRING" "*END-OF-INPUT-READ-TIME*"
- "CLOSE-IO-WINDOW" "TOKENS-TO-STRING" "OPEN-IO-WINDOW"
- "END-OF-INPUT" "ACCEPT-INPUT" "INPUT-PROMISE" "RESTART-INPUT-EDITOR"
- "INITIAL-INPUT-PROMISE" "OPEN-INPUT-EDITOR" "RESTART-IO-WINDOW")
- (:global "OPEN-IO-WINDOW" "CLOSE-IO-WINDOW" "RESTART-IO-WINDOW")
- (:use "PROBES" "BACKTRACE" "TRANSLITERATE" "HISTORIAN"))
-
- )
-
-
- ;;; ================================================================
- ;;; Third file: a loader file.
-
- ;;; The simplest way to load a system is to define a single file which
-
- ;;; 1) Loads the Dungeon Master. The Dungeon Master is a file, not a
- ;;; module, so use LOAD.
-
- (load (make-pathname
- :name "dungeon-master"
- :type nil
- :defaults *load-pathname*))
-
- ;;; 2) Sets up the search path. E.g., to push the directory of the
- ;;; loader file:
-
- (push-module-search-path *load-pathname*)
-
- ;;; 3) Loads any necessary package sets. One could define package sets
- ;;; in modules, rather than files, but this is not recommended.
-
- (load (make-pathname
- :name "all-pkgs"
- :type nil
- :defaults *load-pathname*))
-
- ;;; 4) Loads the toplevel module of the system using REQUIRE-MODULE. If
- ;;; the module is named "toplevel-code", then:
-
- (dm:require-module :toplevel-code)
-
-
- ;;; ================================
-
- ;;; As a slightly more complex example, I'll describe what the MIT LATH NLP
- ;;; loader file does. In this system, there are multiple control modules,
- ;;; any one of which can be loaded on top of the general NLP system. This
- ;;; loader file
- ;;; 1) Loads patches for the lisp configuration.
- ;;; 2) Loads the Dungeon Master. (It also loads a few other extensions
- ;;; to Common Lisp).
- ;;; 3) Sets up a basic search path. It pushes half a dozen directories
- ;;; in which language independent code is stored onto the search
- ;;; path. Then it queries the user for a language to use and pushes
- ;;; a subdirectory for that language onto the search path. Each
- ;;; language's subdirectory contains the same set of modules, so that
- ;;; the appropriate language's module is loaded by virtue of its
- ;;; directory being present on the search path. (Multiple languages
- ;;; can't be loaded simultaneously.
- ;;; 4) Loads the basic NLP package set.
- ;;; 5) Queries the user for a control module to load. Pushes a the
- ;;; control module's subdirectory onto the search path. Chooses a
- ;;; toplevel module to load based on the control module.
- ;;; 6) Loads the toplevel module of the chosen control module using
- ;;; REQUIRE-MODULE.
-
-
- ;;; ================================================================
- ;;; Fourth file: a typical module.
-
- ;;; In an ideal world, package information could be distributed as well as
- ;;; the requiring of modules. We at the MIT LATH typically put package
- ;;; information in modules, commented out. We pretend that the package
- ;;; system is distributed, because it's easier to maintain that way. Using
- ;;; this method, any time a package definition is changed, one must change
- ;;; both the official package set file and the comments in the relevant
- ;;; module.
-
- ;;; <<PARENTHETICAL FLAME ON>>
- ;;;
- ;;; Without completely rewriting the package system, it is impossible to
- ;;; distribute package information across files, since one file in a
- ;;; package might cause symbols to be interned locally in the package (say,
- ;;; by using them as local variables) which coincidentally later turn out
- ;;; to be imported from another package by another file in the same
- ;;; package. However, it should be possible to distribute packages in
- ;;; separate file. Every module would have to contain a call to
- ;;; REQUIRE-PACKAGE at the top, which would ensure that a package-defining
- ;;; module is loaded. (Ideally this call would replace the call to
- ;;; IN-PACKAGE.) All information for the package would have to be present
- ;;; in the package-defining module, rather than being distributed across
- ;;; modules. Still, packages would only be loaded as necessary, rather
- ;;; than possibly unnecessary packages being loaded by the single package
- ;;; set file.
- ;;;
- ;;; To be friendly, one might provide a new set of functions, parallel to
- ;;; USE-PACKAGE, SHADOW, IMPORT, etc., which would be used within modules.
- ;;; These functions would signal errors if the package system was found to
- ;;; be inconsistent with the information specified by the arguments to the
- ;;; new functions. This would provide the appearance of a distributed
- ;;; package system, for development purposes.
- ;;;
- ;;; In such a system, it would be an error if any inconsistencies were
- ;;; found in the package system in a production system, but in a
- ;;; development system, package inconsistencies would always result in
- ;;; errors with reasonable restarts (this latter is already the case in
- ;;; many lisp implementations). But be that as it may...
- ;;;
- ;;; <<PARENTHETICAL FLAME OFF>>
-
- ;;; Remember GLS's Put In Seven Extremely Random User Interface Commands?
- ;;; It still holds, mostly, except that the in-package command must come
- ;;; first, because PROVIDE-MODULE isn't built in to Common Lisp.
-
- ;;; "In": This package is created in the package set file.
- (in-package :typical)
-
- ;;; "Put": There are N modules in this subsystem. This one is named
- ;;; :typical-1. The file name is identical to the module name, except that
- ;;; it may be upper, lower, or mixed case, and may be truncated, according
- ;;; to the limits and abilities of the file system.
- (provide-module :typical-1
- ;; I don't know why, but every time I compile this module in Crufty Lisp,
- ;; I get bogus code.
- #+crufty-lisp :compile #+crufty-lisp nil)
-
-
- ;;; "Seven": Shadow symbols if desired. (Not these symbols, I hope!)
- #||(shadow '(cons car cdr))||#
-
- ;;; "Extremely"
- #||(export '(make-typical-struct typical-struct-slot do-typical-stuff))||#
-
- ;;; "Random User": Do these in pairs, because it's easier to read that way.
- ;;; Using packages was taken care of by the package set file, anyway.
- ;;; Also, making use of the condition system and pretty printer is
- ;;; essentially equivalent to requiring them.
-
- #||(use-condition-system)||#
-
- ;;; We make use of a macro from the :iter module. Say so, so that the DM
- ;;; can arrange to reload and/or compile this module if module :iter
- ;;; changes.
- (require-module :iter :macros t)
- #||(use-package :iter)||#
-
- ;;; Don't have to use a package because module :typical-support is in
- ;;; package :typical. Module :typical-support defines macros and structs
- ;;; which this module makes use of.
- (require-module :typical-support
- :macros t :constructors t :accessors t :modifiers t)
-
- (require-module :typical-data
- ;; These days I'm trying out some experimental new data, but I didn't
- ;; want to touch the old data.
- :path "new-typical-data")
-
- ;;; "Interface"
- ;;; This is the only symbol we want from this package, so we'll import it
- ;;; rather than using the package.
- #||(import '(transliterate:transliterate-string))||#
-
- ;;; "Commands": the contents of the module.
-
-
- ;;; ================================================================
- ;;; General comments.
-
- ;;; The MIT LATH has found the Dungeon Master to be convenient and easy to
- ;;; use, but, due to the size of the system (half a dozen or more
- ;;; subdirectories, around a score of packages, and well over a hundred
- ;;; modules), a few quirks and problems have reared their ugly heads.
-
- ;;; 1) When two modules are mutually dependent in such a way that in order
- ;;; to compile each, the other must be compiled, the Dungeon Master will
- ;;; first compile one, then compile the other, and then recompile the
- ;;; first. This looks strange, but is appropriate behavior.
-
- ;;; 2) It is possible to cause an infinite loop in loading. When one
- ;;; module depends on another such that the second must be loaded before
- ;;; the first can be loaded (typically because the first module loads data
- ;;; using code defined in the second module), the DM must completely load
- ;;; the second module before loading the first. If the second module
- ;;; requires the first module, or requires other modules which eventually
- ;;; require the first module, the DM may get into an infinite loop of
- ;;; nested loading of the two modules in question (and possibly
- ;;; intermediate modules). The only solution is to reorganize one's code
- ;;; to eliminate loading loops.
- ;;;
- ;;; Keep in mind that there are valid situations where the DM may start to
- ;;; load one module, then another, and then the first again, but where at
- ;;; this point the first module will be successfully loaded (after which
- ;;; the second module will load, and then the first will be redundantly
- ;;; loaded again). Whether a loop becomes infinite or terminates at the
- ;;; second iteration depends on the lisp configuration and the keywords
- ;;; passed to the relevant calls to REQUIRE-MODULE.
-
- ;;; 3) Some, if not all, configurations have a limit on the number of file
- ;;; streams that can be open at one time. In a very large system, it may
- ;;; be necessary to rearrange your code to avoid exceeding this limit.
- ;;; Usually, simply rearranging the order of calls to REQUIRE-MODULE in
- ;;; various modules is sufficient to reduce the loading depth. If you
- ;;; require generic tool modules first (which rarely load other modules),
- ;;; then local support modules, then other major substem, you are not
- ;;; likely to run into this problem.
-
- ;;; 4) You must be careful to name your modules such that no two module
- ;;; names are identical for the legal file name length on your
- ;;; architecture, as the DM will truncate module names to the legal file
- ;;; name length. This being the modern world, one is not likely to
- ;;; encounter this problem, thank goodness. However, as recently as five
- ;;; years ago, IBM RTs running AIX had a file name length of a mere 14
- ;;; characters. Subtracting one for a dot, one for an extension, and one
- ;;; for a twiddle (~) for Emacs's sake, this left a usable file name length
- ;;; of eleven characters, and the MIT LATH ran up against this in one
- ;;; instance!
-